Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

Ένα μικρό utility για config files ...

Îåêßíçóå áðü ôï ìÝëïò anjelinio. Τελευταία δημοσίευση από το μέλος anjelinio στις 25-01-2006, 00:12. Υπάρχουν 0 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  25-01-2006, 00:12 8920

    Ένα μικρό utility για config files ...

    Συνημμένα: FileHandler.rar

    Καλησπέρα παιδιά ...

    Εδώ και πολύ καιρό συνηθίζουμε στη δουλεά να χρησιμοποιούμε διάφορα configuration files, στα οποία  κάνουμε κάποια αρχική επεξεργασία για να παράγουμε objects, ui elements κτλ κτλ. Έτσι, μας δημιουργήθηκε η ανάγκη να cache-άρουμε το αποτέλεσμα αυτής της επεξεργασίας στη μνήμη, για να αποφεύγουμε το overhead της συνεχούς και concurrent χρήσης του σκληρού, πάνω στο ίδιο αρχείο.

    Συνέπεια του απο πάνω όμως, ήταν η νέα ανάγκη να είμαστε σίγουροι οτι το cache-αρισμένο αποτέλεσμα ήταν το σωστό, με την έννοιά του οτι αταπολρίνεται στα πριεχόμενα του αρχικού file, όπως αυτά είναι τώρα ( συνηθίζουμε να κάνουμε αλλαγές σε αυτά τα αρχεία at runtime, κύρίως όταν είμαστε σε πελάτη για διάφορα customizations κτλ. ).

    Σαν αποτέλεσμα, έγραψα μια μικρή κλάσση, η οποία αναλαμβάνει τα κάνει τα εξής:

    1. Φορτώνει τα περιεχόμενα ενός File σε ένα MemoryStream ή byte[].
    2. Κάνει μια επεξεργασία αυτών των δεδομένων, μέσω ενός delegate event το οποίο γίνεται raise κάθε φορά που ξαναδιαβάζεται το αρχείο.
    3. Κάνει αυτά τα πράγματα αυτόματα, δεχόμενη events απο ένας FileSystemWatcher τον οποίο δημιουργεί κάθε φορά.

    Συνήθως, ανάλογα το data type το οποίο παράγω απο το εκάστοτε αρχείο ή γενικό τύπο αρχείων (txt, xml, binaries), γράφω μια μικρή subclass, η οποία αναλαμβάνει να υλοποιήσει την specific επεξεργασία των περιεχομένων του αρχείου.  

    Ε, και τώρα είπα να μοιραστώ ... :P

    Η χρήση του FileHandler είναι σχετικά απλή, χρειάζεται ένα όνομα, και ένα path σ'ένα αρχείο (ναί, κι εγώ έχω σκεφτεί οτι και ένα url θα ήταν καλή ιδέα εδώ). Το property Value επιστρέφει την τελευταία τιμή των περιχομένων του αρχείου, και έχει και ένα event, το οποίο καλείται κάθε φορά που γίνεται reload το αρχείο που παρακολουθεί.

    static void Main(string[] args)
    {
     // make sure we stop running gracefully in Ctrl+C ...
     Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);

     // Create the file handler & set the notification method upon reload
     FileHandler handler = new FileHandler("test", Path.Combine(System.Environment.CurrentDirectory, @"..\..\resources\test.txt"), true);
     handler.OnFileLoaded += new FileHandler.ProcessFile(Reloaded);
        
     // ok, now start asking for the value ...
     while (_running)
     {
      Console.WriteLine(ReadString((byte[])handler.Value));
      System.Threading.Thread.Sleep(new TimeSpan(0, 0, 30));
     }
    }

    Εσωτερικά, η ψυχή του FileHandler είναι ένα MemoryStream, το οποίο κάθε φορά περιέχει τα περιεχόμενα του αρχείου σε bytes, ένας FileSystemWatcher ο οποίος παρακολουθεί το συγκεκριμένο αρχείο, και λίγη λογική η οποία αναλαμβάνει να θέτει ένα flag για το όταν χρειάζεται να φορτώσουμε ξανά το αρχείο.

    Αυτά γίνονται κυρίως στη μέθοδο Initialize(string filePath):

    /// <summary>
    /// Performs whatever processing is required upon File load
    /// </summary>
    /// <param name="filePath"></param>
    protected virtual void Initialize(string filePath){
     // exit if we don't need initializing really ...
     if (!m_RequiresInit) return;

     // check for a file that exists
     if (!File.Exists(filePath))
      throw new ArgumentException(string.Format("Could not locate file: {0}", filePath));

     // lock the object, and initialize
     lock (this)
     {
      try
      {
       // store the path internally
       m_FilePath = filePath;

       // ok, now load up the file & set the file watcher
       m_FileBuffer = new MemoryStream();
       using (StreamReader reader = new StreamReader(File.OpenRead(m_FilePath)))
       {
        int byteVal;
        while((byteVal = reader.Read()) != -1)
         m_FileBuffer.WriteByte((byte)byteVal);
       }

       // ok, now we have the contents of the file in-memory as bytes. Process it if required.
       if (null != this.OnFileLoaded)
        this.OnFileLoaded(m_FileBuffer, this);

       // mark we initialized succesfully
       m_RequiresInit = false;

       // handle the FileWatcher now ...
       string fileDir = Path.GetDirectoryName(m_FilePath);
       string fileName = Path.GetFileName(m_FilePath);

       if (null == m_FileWatcher) m_FileWatcher = new FileSystemWatcher(fileDir);
       m_FileWatcher.Filter = fileName;
       m_FileWatcher.NotifyFilter = NotifyFilters.LastWrite;
       m_FileWatcher.Changed += new FileSystemEventHandler(FileWatcher_Changed);
       
       m_FileWatcher.EnableRaisingEvents = true;

       // mark last update time
       m_LastUpdate = (System.DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
      }

      catch {
       // if something went wrong, mark as needing initialization
       m_RequiresInit = true;
       m_FileWatcher.EnableRaisingEvents = false;
       throw;
      }
     }            
    }

    Λίγη προσοχή μόνο χρειάζεται στη μέθοδο την οποία καλεί ο FileSystemWatcher, επειδή γυρίζει παραπάνω απο 1 events για κάθε φορά που αλλάζει το αρχείο που παρακολουθεί. Έτσι, κρατάω τα milliseconds την τελευταία φορά που κλήθηκε η μέθοδος, και αν η διαφορά με τα millis αυτή τη στιγμή είναι μικρότερη ενός threshold value αγνοώ το event.

    private long m_LastUpdate = (System.DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
    private long m_EventIgnoreTimeSpanMillis = 500;

    /// <summary>
    /// Executed whenever the file we're handling is changed
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void FileWatcher_Changed(object sender, FileSystemEventArgs e)
    {
     // we know already ! buzz off !
     if (m_RequiresInit) return;
     lock (this)
     {
      // Could it be that this is one of a sequence of events the FSWatcher annoyingly sends for each write ?
      if(((System.DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - m_LastUpdate) <  m_EventIgnoreTimeSpanMillis)
       return;

      // ok, we need re-initialization !
      m_RequiresInit = true;

      // however, if we need to do that immediately .. let's proceed !
      if (m_InitImmediate) {
       Initialize(m_FilePath);
      }
     }
    }

    Το μόνο που χρειάζεται ακόμη, είναι το property Value το οποίο επιστρεφει τα περιεχόμενα του αρχείου να ελέγχει πρώτα αν χρεάζεται να ξαναδιαβάσει το αρχείο. ( ο FileHandler έχει 2 modes λειτουργίας. Ξαναδιαβάζει το αρχείο αμέσως όταν έρθει event οτι άλλαξε, ή με lazy load όταν ζητηθεί η τιμή και τα περιεχόμενα έχουν αλλάξει, γι'αυτό υπάρχουν 2 constructors, και ένα property, το EnableLazyLoading )

    /// <summary>
    /// Indicates whether the FileHandler will reload the file
    /// it handles immedaitely after any change, or wait until
    /// its value is required
    /// </summary>
    public bool EnableLazyLoading {
     get {
      return m_InitImmediate;
     }
     set {
      m_InitImmediate = value;
     }
    }

     

    /// <summary>
    /// Overridable member that returns the processed contents
    /// of the file we're handling. By default, it will return the
    /// contents of the file as a byte array
    /// </summary>
    public virtual object Value {
     get {
      // Check for needs initialization ...
      if (m_RequiresInit)
       Initialize(m_FilePath);

      // ok, now return the contents of the file
      return m_FileBuffer.ToArray();
     }
    }

    ... αυτά ... καλό πράμα τα χιόνια, σου ξεκλέβουν λίγο χρόνο απο τη δουλειά, που μας πάει 65-μία πλέον η "υπηρεσία" ... :P

    Το attached project rar, είναι σε Visual C# Express 2005 ... δυστυχώς δεν έχω όλο το VS στο σπίτι.

    Υ.Γ. Υποψιάζομαι οτι τα locks στον κώδικα θα μπορούσαν να γίνουν καλύτερα, όπως και ο μηχανισμός με τον delegate και τις virtual μεθόδους, έτσι ώστε αν βοηθά περισσότερο τον implementer μιας subclass .. ίσως επίσης να χρειαζόταν να "αδειάζει" αυτός ο buffer MemoryStream για να τρώει λιγότερα resources, αλλά αν αρχίσω να απαριθμώ τώρα βελτιώσεις, θα πρέπει να χιονίζει για 3 μήνες για να βρώ χρόνο να τις κάνω :D

    Καλό βράδυ !


    Angel
    O:]
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems